// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Mono.Cecil;

namespace Mono.Linker
{
    public class CustomAttributeSource
    {
        public AttributeInfo PrimaryAttributeInfo { get; }
        private readonly Dictionary<AssemblyDefinition, AttributeInfo?> _embeddedXmlInfos;
        readonly LinkContext _context;

        public CustomAttributeSource(LinkContext context)
        {
            PrimaryAttributeInfo = new AttributeInfo();
            _embeddedXmlInfos = new Dictionary<AssemblyDefinition, AttributeInfo?>();
            _context = context;
        }

        public static AssemblyDefinition GetAssemblyFromCustomAttributeProvider(ICustomAttributeProvider provider)
        {
            return provider switch
            {
                MemberReference mr => mr.Module.Assembly,
                AssemblyDefinition ad => ad,
                ModuleDefinition md => md.Assembly,
                InterfaceImplementation ii => ii.InterfaceType.Module.Assembly,
                GenericParameterConstraint gpc => gpc.ConstraintType.Module.Assembly,
                ParameterDefinition pd => pd.ParameterType.Module.Assembly,
                MethodReturnType mrt => mrt.ReturnType.Module.Assembly,
                _ => throw new NotImplementedException(provider.GetType().ToString()),
            };
        }

        public bool TryGetEmbeddedXmlInfo(ICustomAttributeProvider provider, [NotNullWhen(true)] out AttributeInfo? xmlInfo)
        {
            AssemblyDefinition assembly = GetAssemblyFromCustomAttributeProvider(provider);

            if (!_embeddedXmlInfos.TryGetValue(assembly, out xmlInfo))
            {
                // Add an empty record - this prevents reentrancy
                // If the embedded XML itself generates warnings, trying to log such warning
                // may ask for attributes (suppressions) and thus we could end up in this very place again
                // So first add a dummy record and once processed we will replace it with the real data
                _embeddedXmlInfos.Add(assembly, new AttributeInfo());
                xmlInfo = _context.EmbeddedXmlInfo.ProcessAttributes(assembly, _context);
                _embeddedXmlInfos[assembly] = xmlInfo;
            }

            return xmlInfo != null;
        }

        public IEnumerable<CustomAttribute> GetCustomAttributes(ICustomAttributeProvider provider, string attributeNamespace, string attributeName)
        {
            foreach (var attr in GetCustomAttributes(provider))
            {
                if (attr.AttributeType.Namespace == attributeNamespace && attr.AttributeType.Name == attributeName)
                    yield return attr;
            }
        }

        public IEnumerable<CustomAttribute> GetCustomAttributes(ICustomAttributeProvider provider)
        {
            if (provider.HasCustomAttributes)
            {
                foreach (var customAttribute in provider.CustomAttributes)
                    yield return customAttribute;
            }

            if (PrimaryAttributeInfo.CustomAttributes.TryGetValue(provider, out var annotations))
            {
                foreach (var customAttribute in annotations)
                    yield return customAttribute;
            }

            if (!TryGetEmbeddedXmlInfo(provider, out var embeddedXml))
                yield break;

            if (embeddedXml.CustomAttributes.TryGetValue(provider, out annotations))
            {
                foreach (var customAttribute in annotations)
                    yield return customAttribute;
            }
        }

        public bool TryGetCustomAttributeOrigin(ICustomAttributeProvider provider, CustomAttribute customAttribute, out MessageOrigin origin)
        {
            if (PrimaryAttributeInfo.CustomAttributesOrigins.TryGetValue(customAttribute, out origin))
                return true;

            if (!TryGetEmbeddedXmlInfo(provider, out var embeddedXml))
                return false;

            return embeddedXml.CustomAttributesOrigins.TryGetValue(customAttribute, out origin);
        }

        public bool HasAny(ICustomAttributeProvider provider)
        {
            if (provider.HasCustomAttributes)
                return true;

            if (PrimaryAttributeInfo.CustomAttributes.ContainsKey(provider))
                return true;

            if (!TryGetEmbeddedXmlInfo(provider, out var embeddedXml))
                return false;

            return embeddedXml.CustomAttributes.ContainsKey(provider);
        }
    }
}
